package rpc

import (
	"fmt"
	"time"

	myactor "gitee.com/nggs/actor"
	"gitee.com/nggs/protoactor-go/actor"
)

const (
	defaultPromiseTimeoutDuration = 5 * time.Second
	maxPromiseTimeoutDuration     = 30 * time.Second
)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type PromiseSeq = uint64

type PromiseCallback = func(promise *Promise)
type PromiseOnComplete = func(err error)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type PromiseContext struct {
	Sender           *actor.PID
	IRequestMessage  IMessage
	Receiver         *actor.PID
	IResponseMessage IMessage
	Requested        bool
	Err              error
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
type Promise struct {
	*PromiseContext
	seq             PromiseSeq
	executeTime     time.Time
	timeoutDuration time.Duration
	timeout         time.Time
	callbacks       []PromiseCallback
	callbackCursor  int
	onCompletes     []PromiseOnComplete
	completed       bool
}

func (p *Promise) WithRequest(sender *actor.PID, iReqMsg IMessage, receiver *actor.PID, cb PromiseCallback) *Promise {
	p.Sender = sender
	p.IRequestMessage = iReqMsg
	p.Receiver = receiver
	p.callbacks = append(p.callbacks, cb)
	p.Requested = false
	p.Err = nil
	return p
}

func (p *Promise) WithTimeout(timeout time.Duration) *Promise {
	p.timeoutDuration = timeout
	if p.timeoutDuration > maxPromiseTimeoutDuration {
		p.timeoutDuration = maxPromiseTimeoutDuration
	}
	p.timeout = p.executeTime.Add(p.timeoutDuration)
	return p
}

func (p *Promise) Then(cb PromiseCallback) *Promise {
	if cb == nil {
		return p
	}
	p.callbacks = append(p.callbacks, cb)
	return p
}

func (p *Promise) Add(child *Promise) *Promise {
	p.WithTimeout(p.timeoutDuration + child.timeoutDuration)

	if !myactor.IsPIDPtrEmpty(child.Sender) && child.IRequestMessage != nil && !myactor.IsPIDPtrEmpty(child.Receiver) && !p.Requested {
		p.callbacks = append(p.callbacks, func(promise *Promise) {
			promise.Sender = child.Sender
			promise.IRequestMessage = child.IRequestMessage
			promise.Receiver = child.Receiver
		})
	}

	p.callbacks = append(p.callbacks, child.callbacks...)
	return p
}

func (p *Promise) OnComplete(cb PromiseOnComplete) *Promise {
	p.onCompletes = append(p.onCompletes, cb)
	return p
}

func (p *Promise) Execute() {
	for p.callbackCursor < len(p.callbacks) {
		if !myactor.IsPIDPtrEmpty(p.Sender) && p.IRequestMessage != nil && !myactor.IsPIDPtrEmpty(p.Receiver) && !p.Requested {
			if err := p.request(); err != nil {
				p.onComplete(err)
			}
			p.Requested = true
			break
		}
		p.callbacks[p.callbackCursor](p)
		if p.PromiseContext.Err != nil {
			p.onComplete(p.PromiseContext.Err)
			return
		}
		p.callbackCursor++
	}
	if p.callbackCursor > len(p.callbacks)-1 {
		p.onComplete(nil)
	}
	return
}

func (p *Promise) request() (err error) {
	reqMsgData, err := p.IRequestMessage.Marshal()
	if err != nil {
		err = fmt.Errorf("marshal request message fail, message name=%s, %s", p.IRequestMessage.MessageName(), err)
		p.onComplete(err)
		return
	}

	p.executeTime = time.Now()
	p.timeout = p.executeTime.Add(p.timeoutDuration)

	send := Get_C2S_Promise()
	send.Seq = p.seq
	send.ReqMsgID = p.IRequestMessage.MessageID()
	send.ReqMsgData = reqMsgData
	myactor.RootContext.RequestWithCustomSender(p.Receiver, send, p.Sender)
	return
}

func (p *Promise) onResponse(iRespMsg IMessage) (completed bool) {
	for p.callbackCursor < len(p.callbacks) {
		if !myactor.IsPIDPtrEmpty(p.Sender) && p.IRequestMessage != nil && !myactor.IsPIDPtrEmpty(p.Receiver) && !p.Requested {
			if err := p.request(); err != nil {
				p.onComplete(err)
				return true
			}
			p.Requested = true
			break
		}
		p.IResponseMessage = iRespMsg
		p.Requested = false
		p.callbacks[p.callbackCursor](p)
		if p.PromiseContext.Err != nil {
			p.onComplete(p.PromiseContext.Err)
			return true
		}
		p.callbackCursor++
	}
	if p.callbackCursor > len(p.callbacks)-1 {
		p.onComplete(nil)
		return true
	}
	return false
}

func (p *Promise) onComplete(err error) {
	for _, cb := range p.onCompletes {
		cb(err)
	}
	p.completed = true
}
